﻿using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net;
using OpenGameEngine.Logging;
using System.Threading;
using System.Text;

namespace OpenGameEngine.Net.Server
{
    public class ServerBase
    {
        public delegate void PacketHandler(ServerClientBase client, ServerBase server, IPacket packet);

        public delegate void HandleClientAsync(IAsyncResult result);

        public List<ServerClientBase> Clients { get; private set; }

        public TcpListener Listener { get; private set; }

        private object NetworkLock;

        private PacketHandler[] PacketHandlerDelegates { get; set; }

        private Boolean NetworkWorkerRunning { get; set; }

        public event EventHandler<ServerClientEventArgs> ClientConnected;
        public event EventHandler<ServerClientDisconnectedEventArgs> ClientDisconnected;
        public event EventHandler<ServerPacketReceivedEventArgs> PacketReceived;

        public ServerBase(IPEndPoint endPoint)
        {
            Clients = new List<ServerClientBase>();
            Listener = new TcpListener(endPoint);
            NetworkLock = new object();
            NetworkWorkerRunning = false;
            PacketHandlerDelegates = new PacketHandler[256];
        }

        public void Start()
        {
            LogProvider.Log("Starting server...", LogImportance.Medium);

            ServerStopped = false;

            Listener.Start();
            Listener.BeginAcceptTcpClient(AcceptConnectionAsync, null);

            NetworkWorkerRunning = true;

            OnServerStarting();

            LogProvider.Log("Server started!", LogImportance.Medium);
        }

        internal bool ServerStopped = true;

        protected internal virtual void OnServerStarting()
        {

        }

        public void Stop()
        {
            LogProvider.Log("Stopping server...", LogImportance.Medium);

            OnServerStopping();

            NetworkWorkerRunning = false;

            LogProvider.Log("Waiting for server to stop...", LogImportance.Medium);

            Thread.Sleep(10);

            if (!ServerStopped)
            {
                ServerClientBase[] bs = this.Clients.ToArray();
                foreach (ServerClientBase b in bs)
                {
                    if (b.IsLoggedIn)
                    {
                        if (b.TcpClient.Connected)
                        {
                            b.TcpClient.Client.BeginDisconnect(true, null, null);
                        }

                        b.IsLoggedIn = false;
                    }
                }

                if (bs.Length == 0)
                    ServerStopped = true;
            }

            if (Listener != null)
            {
                Listener.Stop();
                Listener = null;
            }

            LogProvider.Log("Server stopped.", LogImportance.Medium);
        }

        public virtual void OnServerStopping()
        {

        }

        public void RegisterPacketHandler(Type packetType, PacketHandler handler)
        {
            IPacket packet = PacketReader.PacketFromType(packetType);
            byte packetId = packet.Id;
            PacketHandlerDelegates[packetId] = handler;
            PacketReader.OverridePacket(packetType);
        }

        protected internal virtual ServerClientBase CreateServerClient(TcpClient client, ServerBase server)
        {
            return new ServerClientBase(client, server);
        }

        public bool RemoveClient(ServerClientBase client)
        {
            lock (NetworkLock)
                return Clients.Remove(client);
        }

        private void AcceptConnectionAsync(IAsyncResult result)
        {
            try
            {
                if (Listener == null || result == null) return;

                lock (NetworkLock)
                {
                    TcpClient tcpClient = Listener.EndAcceptTcpClient(result);
                    ServerClientBase client = CreateServerClient(tcpClient, this);

                    Clients.Add(client);

                    AsyncCallback cb = new AsyncCallback(RestartClientHandler);

                    LogProvider.Log("Information: Accepted client connection!", LogImportance.Medium);

                    client.BeginHandleClient(cb, client);

                    Listener.BeginAcceptTcpClient(AcceptConnectionAsync, null);
                }
            }
            catch (Exception e)
            {
                LogProvider.Log(e.Message + "\n" + e.StackTrace, LogImportance.High);
            }
        }

        private void RestartClientHandler(IAsyncResult result)
        {
            AsyncState asyncState = (AsyncState)result.AsyncState;
            ServerClientBase client = (ServerClientBase)asyncState.Object;

            client.EndHandleClient(result);

            if (client.Connected)
                client.BeginHandleClient(RestartClientHandler, client);
        }

        public virtual void OnClientDisconnected(ServerClientBase client, string reason)
        {
            LogProvider.Log("Info: " + client.Username + " disconnected.", LogImportance.Medium);

            if (ClientDisconnected != null)
                ClientDisconnected(this, new ServerClientDisconnectedEventArgs(client, reason));

            RemoveClient(client);
        }

        public void HandlePacket(ServerClientBase client, IPacket packet)
        {
            if (PacketHandlerDelegates[packet.Id] == null)
            {
                String m = "No packet handler set for 0x" + packet.Id.ToString("X2");

                LogProvider.Log(new InvalidOperationException(m));
            }
            else
            {
                if (PacketReceived != null)
                    PacketReceived(this, new ServerPacketReceivedEventArgs(client, packet));

                PacketHandlerDelegates[packet.Id](client, this, packet);
            }
        }
    }
}
